﻿namespace Neovolve.Bootstrapper.Wizard
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using EnvDTE;

    /// <summary>
    /// The <see cref="DteExtensions"/>
    ///   class provides extension methods for the <see cref="DTE"/> class.
    /// </summary>
    internal static class DteExtensions
    {
        /// <summary>
        /// Defines the miscellaneous files project kind.
        /// </summary>
        private const String VsProjectKindMisc = "{66A2671D-8FB5-11D2-AA7E-00C04F688DDE}";

        /// <summary>
        /// Defines the solution folder project kind.
        /// </summary>
        private const String VsProjectKindSolutionFolder = "{66A26720-8FB5-11D2-AA7E-00C04F688DDE}";

        /// <summary>
        /// Defines the wix project kind.
        /// </summary>
        private const String VsProjectKindWix = "{930C7802-8A8C-48F9-8165-68863BCCD9DD}";

        /// <summary>
        /// Returns the deployment projects available in the specified <see cref="DTE"/>.
        /// </summary>
        /// <param name="dte">
        /// The dte reference.
        /// </param>
        /// <returns>
        /// A list of deployment projects.
        /// </returns>
        public static IList<Project> DeploymentProjects(this DTE dte)
        {
            List<Project> results = new List<Project>();
            IEnumerable<Project> solutionProjects = dte.Solution.Projects.OfType<Project>();

            PopulateDeploymentProjects(solutionProjects, results);

            return results;
        }

        /// <summary>
        /// Gets the full name of the output.
        /// </summary>
        /// <param name="source">
        /// The source.
        /// </param>
        /// <returns>
        /// A <see cref="String"/> instance.
        /// </returns>
        public static String GetOutputFullName(this Project source)
        {
            if (source == null)
            {
                return String.Empty;
            }

            if (source.Kind == VsProjectKindSolutionFolder)
            {
                return String.Empty;
            }

            if (source.Kind == VsProjectKindMisc)
            {
                return String.Empty;
            }

            String fullPath = source.Properties.GetProperty("FullPath");

            if (String.IsNullOrEmpty(fullPath))
            {
                return String.Empty;
            }

            String outputPath = source.ConfigurationManager.ActiveConfiguration.Properties.GetProperty("OutputPath");

            if (String.IsNullOrEmpty(outputPath))
            {
                return String.Empty;
            }

            String outputFile = source.Properties.GetProperty("OutputFileName");

            if (String.IsNullOrWhiteSpace(outputFile))
            {
                return String.Empty;
            }

            if (VsProjectKindWix.Equals(source.Kind, StringComparison.OrdinalIgnoreCase))
            {
                String outputName = source.Properties.GetProperty("OutputName");

                String strippedOutputFile = Path.GetFileNameWithoutExtension(outputFile);

                if (strippedOutputFile.Equals(outputName, StringComparison.OrdinalIgnoreCase) == false)
                {
                    // This catches a bug in WiX 3.5 where sometimes the OutputFileName property does not accurately reflect the OutputName property
                    String extension = Path.GetExtension(outputFile);

                    outputFile = outputName + extension;
                }
            }

            return Path.Combine(fullPath, outputPath, outputFile);
        }

        /// <summary>
        /// Gets the property.
        /// </summary>
        /// <param name="properties">
        /// The properties.
        /// </param>
        /// <param name="propertyName">
        /// Name of the property.
        /// </param>
        /// <returns>
        /// A <see cref="String"/> instance.
        /// </returns>
        public static String GetProperty(this Properties properties, String propertyName)
        {
            if (properties == null)
            {
                return String.Empty;
            }

            IEnumerable<Property> propertySet = properties.OfType<Property>();
            Property matchingProperty = propertySet.FirstOrDefault(x => x.Name == propertyName);

            if (matchingProperty == null)
            {
                return String.Empty;
            }

            return matchingProperty.Value.ToString();
        }

        /// <summary>
        /// Determines whether Visual Studio will save a project when it is created without a solution.
        /// </summary>
        /// <param name="dte">
        /// The DTE.
        /// </param>
        /// <returns>
        /// <c>true</c> if Visual Studio will save a project when it is created without a solution; otherwise, <c>false</c>.
        /// </returns>
        public static Boolean IsSavingProjectWhenCreated(this DTE dte)
        {
            IEnumerable<Property> properties = dte.Properties["Environment", "ProjectsAndSolution"].OfType<Property>();
            Property saveProjectProperty = properties.FirstOrDefault(x => x.Name == "SaveNewProjects");

            if (saveProjectProperty == null)
            {
                // We need to be defensive and assume that the project is not being saved.
                // This will result in an absolute package path being assigned in the resource file by the wizard
                return false;
            }

            Boolean setting;
            String saveNewProjectsValue = saveProjectProperty.Value.ToString();

            if (Boolean.TryParse(saveNewProjectsValue, out setting) == false)
            {
                // Same logic as previous test
                return false;
            }

            return setting;
        }

        /// <summary>
        /// Populates deployment projects from the enumerable set of projects.
        /// </summary>
        /// <param name="projects">
        /// The projects.
        /// </param>
        /// <param name="results">
        /// The project results found so far.
        /// </param>
        private static void PopulateDeploymentProjects(IEnumerable<Project> projects, List<Project> results)
        {
            IEnumerable<Project> matchingProjects = from x in projects
                                                    where x.GetOutputFullName().EndsWith(".msi", StringComparison.OrdinalIgnoreCase)
                                                    select x;

            if (matchingProjects.Any())
            {
                results.AddRange(matchingProjects);
            }

            IEnumerable<Project> folderProjects = from x in projects
                                                  where VsProjectKindSolutionFolder.Equals(x.Kind, StringComparison.OrdinalIgnoreCase)
                                                  select x;

            foreach (Project folderProject in folderProjects)
            {
                IEnumerable<Project> subProjects = from x in folderProject.ProjectItems.OfType<ProjectItem>()
                                                   where x.SubProject != null
                                                   select x.SubProject;

                PopulateDeploymentProjects(subProjects, results);
            }
        }
    }
}